<!-- openai 聊天框 -->
<template>
    <div class="chat-wrapper">
        <!-- 聊天框头部内容 -->
        <div class="chat-header">
            <strong>{{ title }}</strong>
            <el-tooltip
                    v-if="messages && messages.length > 0 "
                    class="item"
                    effect="dark"
                    content="清空对话记录"
                    placement="bottom">
                <i class="el-icon-refresh-right"
                   :class="{'rotate-animation': control.clearChatRecord }"
                   @click="clearChatRecordEvent"/>
            </el-tooltip>
        </div>
        <!-- 聊天框主题 -->
        <div class="chat-body" ref="messagesContainer">
            <template v-for="message in messages">
                <!-- 用户消息 -->
                <div v-if="message.role == 'user'" class="message user-message">
                    <div class="avatar">
                        <img src="./avatar/user.png" >
                    </div>
                    <div class="message-content">
                        {{ message.content }}
                    </div>
                </div>
                <!-- AI消息 -->
                <div v-if="message.role == 'assistant'" class="message ai-message">
                    <div class="avatar">
                        <img src="./avatar/ai.png" >
                    </div>
                    <!-- 等待AI响应 -->
                    <dot-flashing class="chat-load" v-if="!message.content && !message.error && message.extendMsgs.length == 0"/>
                    <!-- AI 消息 -->
                    <div class="message-content" v-if="(message.content || message.extendMsgs.length > 0) && !message.error">
                        <!-- 渲染扩展消息 -->
                        <template
                                v-if="message.extendMsgs && message.extendMsgs.length > 0"
                                v-for="msg in message.extendMsgs">
                            <!-- 思考内容 -->
                            <template v-if="msg.type == 'think'">
                                <div class="message-operate" @click="clickThinkEvent(msg)">
                                    <span>
                                        深度思考({{ msg.duration / 1000 }})秒
                                    </span>
                                    <i class="el-icon-arrow-down arrow-icon" :class="{'up': msg.showThink}"/>
                                </div>
                                <div v-if="msg.showThink" class="think-content">
                                    {{ msg.think }}
                                </div>
                            </template>
                            <!-- 工具调用 -->
                            <template v-if="msg.type == 'function'">
                                <div class="message-operate" @click="clickToolCallEvent(msg)">
                                    <span>
                                        工具调用({{ msg.function.name }})
                                    </span>
                                </div>
                            </template>
                        </template>
                        <!-- AI响应内容 -->
                        <template v-if="message.htmlContent">
                            <div
                                class="markdown-body"
                                v-html="message.htmlContent">
                            </div>
                        </template>
                    </div>
                    <div v-if="message.error" class="message-content error-message-content">
                        {{message.error}}
                    </div>
                </div>
            </template>
        </div>
        <!-- 聊天框底部内容 -->
        <div class="chat-footer">
            <el-input
                    :rows="control.inputRows"
                    @keydown.native.enter.exact.prevent="inputMessageKeyHandle('enter')"
                    @keydown.native.shift.enter.stop="inputMessageKeyHandle('shift_enter')"
                    @keyup.native.backspace="inputMessageKeyHandle('backspace')"
                    @keydown.native.backspace="inputMessageKeyHandle('backspace')"
                    v-model="inputMessage"
                    type="textarea"
                    placeholder="按 shift + enter 换行, 按 enter 发送消息"
                    :disabled="control.chatting"
                    resize="none"/>
            <el-button type="primary" size="small" @click="sendMessage" :disabled="control.chatting">发送</el-button>
        </div>

        <!-- 查看工具调用函数入参 -->
        <el-dialog title="查看工具调用函数入参"
                   :close-on-click-modal="false"
                   :visible.sync="dialog.function.visible">
            <el-input
                    type="textarea"
                    :rows="10"
                    v-model="dialog.function.functionJson"
                    resize="none"
                    :readonly="true"/>
        </el-dialog>

    </div>
</template>

<script>
    /* markdown 渲染 */
    import MarkdownIt from 'markdown-it'
    import hljs from 'highlight.js'
    import 'highlight.js/styles/github.css'

    import dotFlashing from "./DotFlashing"
    import tools from "@/util/Tools"

    export default {
        name: "OpenAiChat",
        components: {
            dotFlashing
        },
        props: {
            /* agent name */
            agentName: {
                type: String,
                require: true,
            },
            /* 用户名称 */
            username: {
                type: String,
                require: true,
            },
            /* 聊天框标题 */
            title: {
                type: String,
                require: true,
            },
            /* 聊天请求地址 */
            chatURL: {
                type: String,
                require: true,
            },
            /* 对话记录请求地址 */
            recordURL: {
                type: String,
                default: "/chat/record/findAgentRecord"
            },
            /* 对话请求变量参数 */
            timeout: {
                type: Number,
                require: false,
            },
            /* 对话请求变量参数 */
            variables: {
                type: Object,
                require: false,
            }
        },
        data() {
            return {
                /* 输入框消息 */
                inputMessage: "",
                /* 聊天消息 */
                messages: [ ],
                /* 页面元素控制 */
                control: {
                    /* 聊天框输入行数 */
                    inputRows: 1,
                    /* 点击清理按钮，设置 true。清理结束设置false */
                    clearChatRecord: false,
                    /* 是否正在对话, 当发送消息时, 设置为true */
                    chatting: false,
                },
                /* 弹窗 */
                dialog: {
                    /* 查看工具调用 */
                    function: {
                        visible: false,
                        functionJson: ""
                    }
                },
                // markdown
                md: null,
            }
        },
        methods: {
            // Markdown 渲染
            renderMarkdown(text) {
                return this.md.render(text || '')
            },

            /* 发送消息 */
            sendMessage() {
                if (checkSendMessage(this)) {
                    // 输入消息
                    let input = this.inputMessage;
                    // 设置正在对话为true
                    this.control.chatting = true;
                    // 添加到消息列表
                    this.messages.push({role: "user", content: input});
                    // 输入框置空
                    this.inputMessage = "";
                    // 添加一条AI消息, 后续的响应都将添加到该消息上
                    this.messages.push({role: "assistant", extendMsgs: [], content: "", htmlContent: ""});

                    // 设置滚动到底部
                    this.messagesContainerScrollToBottom();

                    // 发起聊天
                    aiAgentChat(this, input, (data, down) => {
                        console.log("data", JSON.stringify(data))
                        // 对话完成
                        if (down) {
                            this.control.chatting = false;
                            return;
                        }

                        // 获取 ai message
                        let aiMessage = this.messages[this.messages.length - 1];

                        // 思考内容
                        if (data.think) {
                            let extendMsgs = aiMessage.extendMsgs;

                            // 需要添加思考内容消息
                            if (extendMsgs.length == 0 || extendMsgs[extendMsgs.length - 1].type != "think") {
                                // 添加思考内容
                                extendMsgs.push({
                                    type: "think",
                                    showThink: true,
                                    startTime: data.timestamp,
                                    think: data.think,
                                    duration: 0
                                });
                            } else {
                                // 获取最后一条数据填入
                                let extendMsg = extendMsgs[extendMsgs.length - 1];
                                extendMsg.think += data.think;
                                extendMsg.duration = data.timestamp - extendMsg.startTime;
                            }
                        }

                        // 工具调用
                        if (data.function) {
                            let extendMsgs = aiMessage.extendMsgs;
                            if (extendMsgs.length > 0 && extendMsgs[extendMsgs.length - 1].type == "think") {
                                // 设置思考内容隐藏
                                extendMsgs[extendMsgs.length - 1].showThink = false;
                            }
                            extendMsgs.push({
                                type: "function",
                                function: data.function,
                            });
                        }

                        // 内容
                        if (data.content) {
                            let extendMsgs = aiMessage.extendMsgs;
                            if (extendMsgs.length > 0 && extendMsgs[extendMsgs.length - 1].type == "think") {
                                // 设置思考内容隐藏
                                extendMsgs[extendMsgs.length - 1].showThink = false;
                            }
                            // 设置响应内容
                            aiMessage.content += data.content;
                            aiMessage.htmlContent = this.renderMarkdown(aiMessage.content);
                        }

                        // 设置滚动到底部
                        this.messagesContainerScrollToBottom();
                    }, error => {
                        let aiMessage = this.messages[this.messages.length - 1];
                        this.$set(aiMessage, "error", "系统服务异常, 请稍后再试");
                        this.control.chatting = false;
                    });
                }
            },

            /* 查找聊天记录 */
            findChatRecord() {
                tools.post(this.recordURL, {
                    agentName: this.agentName,
                    username: this.username,
                    /* 查询最近条目数 */
                    //limit: 0,
                }).then(response => {
                    console.log("聊天记录", response)
                    if (response.data) {
                        response.data.forEach(message => {
                            if (message.role == "assistant" && message.content) {
                                message.htmlContent = this.renderMarkdown(message.content);
                            }
                        })
                    }
                    this.messages = response.data;

                    // message聊天框滚动到最低部
                    this.messagesContainerScrollToBottom();
                });
            },

            /**
             * 点击清空聊天记录事件
             */
            clearChatRecordEvent() {
                if (this.control.chatting) {
                    this.$message.error("当前有正在进行的对话, 需等待对话结束后才能删除");
                    return;
                }

                this.$confirm("将清理在此Agent下的所有聊天记录", "提示", {
                    confirmButtonText: "确定",
                    cancelButtonText: "取消",
                    type: "warning"
                }).then(() => {
                    this.control.clearChatRecord = true;
                    tools.post("/chat/record/delete", {
                        agentName: this.agentName,
                        username: this.username
                    }).then(response => {
                        this.$message({
                            type: "success",
                            message: "删除成功"
                        });
                        this.control.clearChatRecord = false;
                        this.messages = [];
                    })
                }).catch(() => {
                });
            },

            /* 点击深度思考事件 */
            clickThinkEvent(msg) {
                this.$set(msg, "showThink", msg.showThink ? false : true);
            },

            /* 点击工具调用事件 */
            clickToolCallEvent(msg) {
                let func = {
                    name: msg.function.name,
                    arguments: JSON.parse(msg.function.arguments)
                }
                this.dialog.function.functionJson = JSON.stringify(func, null, 2);
                this.dialog.function.visible = true;
            },

            /* 聊天输入框 按键事件 */
            inputMessageKeyHandle(key) {
                if (key == "enter") {
                    // 回车
                    this.sendMessage();
                } else if (key == "shift_enter") {
                    // 换行
                    this.control.inputRows = this.control.inputRows + 1;
                } else if (key == "backspace") {
                    // 退格, 删除内容
                    const lineCount = this.inputMessage.split("\n").length;
                    this.control.inputRows = Math.max(1, lineCount);
                }
            },

            /**
             * message聊天框滚动到最低部
             */
            messagesContainerScrollToBottom() {
                this.$nextTick(() => {
                    const container = this.$refs.messagesContainer;
                    container.scrollTop = container.scrollHeight;
                })
            },
        },
        created() {
            // 初始化Markdown渲染器
            this.md = new MarkdownIt({
                html: true,
                linkify: true,
                typographer: true,
                highlight: function (str, lang) {
                    if (lang && hljs.getLanguage(lang)) {
                        try {
                            return hljs.highlight(str, { language: lang }).value
                        } catch (__) {}
                    }
                    return '' // 使用额外的默认转义
                }
            });
        },
        mounted() {
            this.findChatRecord();
        }
    }

    /**
     * 检查是否可以发送消息
     */
    function checkSendMessage(that) {
        if (!that.inputMessage) {
            that.$message.error("输入消息为空");
            return false;
        } else if (that.control.chatting) {
            that.$message.error("当前有未完成的对话");
            return false;
        }
        return true;
    }

    function aiAgentChat(that, input, callback, errorCallback) {
        fetch(that.chatURL, {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
            },
            body: JSON.stringify({
                agentName: that.agentName,
                username: that.username,
                variables: that.variables,
                timeout: that.timeout,
                input: input
            })
        }).then(response => {
            if (response.ok && response.body) {
                return response.body;
            } else {
                throw new Error("系统服务异常");
            }
            console.log("response: ", response)
        }).then(body => {
            console.log("body: ", body)
            const reader = body.getReader();
            const decoder = new TextDecoder();

            // 读取流数据粘包后存储起来的缓冲区
            let chunkBuffer = "";

            // 读取数据
            function readStream() {
                reader.read().then(({ done, value }) => {
                    if (done) {
                        //console.log('SSE stream closed');
                        return;
                    }
                    // 接收流数据
                    let chunk = decoder.decode(value, { stream: true });
                    // 数据块需要加上未处理完的缓冲区数据
                    let tmpBuf = chunkBuffer + chunk;
                    // 解析data数据
                    let data = "";
                    for (let i = 0; i < tmpBuf.length; i++) {
                        // 检查是否是 \n\n
                        if (tmpBuf[i] == "\n" && tmpBuf[i + 1] == "\n") {
                            if (callback) {
                                // 回调数据快
                                let chunkData = data.replace("data: ", "");
                                if (chunkData == "[DONE]") {
                                    // 回调结束
                                    callback(null, true);
                                } else {
                                    // 回调数据快
                                    callback(JSON.parse(chunkData));
                                }
                            }
                            data = "";
                            i = i + 1;
                        } else {
                            // 添加数据
                            data += tmpBuf[i];
                        }
                    }

                    // 如果data数据是不完整的，加载到缓冲区
                    chunkBuffer = data;

                    // 继续读取下一段
                    readStream();
                }).catch(error => {
                    console.error("Error reading stream", error);
                });
            }

            // 读取流数据
            readStream();
        }).catch(error => {
            if (errorCallback) {
                errorCallback(error);
            }
            console.error("发送消息失败 ", error)
        });
    }

</script>

<style type="scss" scoped>
    .rotate-animation {
        animation: rotate 1.5s linear infinite;
    }

    .chat-wrapper {
        display: flex;
        flex-direction: column;
        height: 100%;
        width: 100%;
    }

    .chat-header {
        height: 40px;
        display: flex;
        justify-content: space-between;
        align-items: center;
        background-color: rgba(241, 235, 235, 0.99);
        padding: 10px;
        box-sizing: border-box;

        i {
            font-size: 20px;
        }
        i:hover {
            color: #ee9090;
            cursor: pointer;
        }
    }

    .chat-body {
        flex: 1;
        box-sizing: border-box;
        overflow: auto;
        overflow-x: hidden;
        background: rgba(231, 230, 230, 0.40);
        /*background: rgba(191, 11, 235, 0.5);*/
        padding: 0 10px 10px 10px;
    }
    .chat-body::-webkit-scrollbar {
        display: none;
    }

    .chat-footer {
        min-height: 40px;
        box-sizing: border-box;
        display: flex;
        align-items: flex-end;
        gap: 16px;
        background-color: #f1eeee;
        padding: 4px;
    }

    .avatar {
        display: inline-block;
        box-sizing: border-box;
        text-align: center;
        overflow: hidden;
        width: 40px;
        height: 40px;
        line-height: 40px;
        padding: 4px;
        background: rgba(209, 212, 217, 0.3);

        img {
            width: 100%;
            height: 100%;
            object-fit: contain;
        }
    }

    .message {
        display: flex;
        gap: 10px;
        margin-top: 10px;
    }

    .user-message {
        flex-direction: row-reverse;
    }

    .message-content {
        display: flex;
        align-items: center;
        padding: 4px 10px;
        word-break: break-word;
        max-width: 70%;
        border-radius: 8px;
        font-size: 16px;
        line-height: 1.6;
        min-height: 40px;
        box-sizing: border-box;
        gap: 8px;
        letter-spacing: 0.1em;
    }

    .user-message .message-content {
        background-color: #c4d8f5;
    }

    .ai-message .message-content {
        background-color: white;
        flex-direction: column;
        justify-content: center;
        align-items: flex-start;
    }

    .message-operate {
        height: 32px;
        display: flex;
        align-items: center;
        border-radius: 5px;
        background: rgba(230, 230, 230, 0.7);
        box-sizing: border-box;
        cursor: pointer;
        font-size: 14px;
        font-weight: 500;
        padding: 0 10px;
    }
    .message-operate:hover {
        background: rgba(230, 230, 230);
    }

    .think-content {
        color: #939191;
        padding: 0 8px;
        border-left: 0.1px solid #939191;
        font-size: 14px;
    }

    .error-message-content {
        background-color: #bd6079 !important;
        color: white;
    }

    .chat-load {
        display: flex;
        align-items: center;
    }

    .arrow-icon {
        transition: transform 0.3s ease;
    }
    .arrow-icon.up {
        transform: rotate(180deg);
    }
    ::v-deep .el-dialog__wrapper {
        ::v-deep .el-textarea__inner {
            background: black !important;
            color: white;
        }
    }


    /* Markdown内容样式 */
    .markdown-body {
        font-size: 16px;
        line-height: 1.5;
        word-wrap: break-word;
    }

    .markdown-body >>> h1,
    .markdown-body >>> h2,
    .markdown-body >>> h3,
    .markdown-body >>> h4,
    .markdown-body >>> h5,
    .markdown-body >>> h6 {
        margin-top: 8px;
        margin-bottom: 8px;
        font-weight: 600;
        line-height: 1.25;
    }

    .markdown-body >>> h1 {
        padding-bottom: 0.3em;
        font-size: 2em;
        border-bottom: 1px solid #eaecef;
    }

    .markdown-body >>> h2 {
        padding-bottom: 0.3em;
        font-size: 1.5em;
        border-bottom: 1px solid #eaecef;
    }

    .markdown-body >>> h3 {
        font-size: 1.25em;
    }

    .markdown-body >>> p {
        margin: 0;
    }

    .markdown-body >>> ul,
    .markdown-body >>> ol {
        padding-left: 2em;
        margin-top: 0;
        margin-bottom: 16px;
    }

    .markdown-body >>> li {
        margin-top: 0.25em;
    }

    .markdown-body >>> blockquote {
        padding: 0 1em;
        color: #6a737d;
        border-left: 0.25em solid #dfe2e5;
        margin: 0 0 16px 0;
    }

    .markdown-body >>> pre {
        padding: 16px;
        overflow: auto;
        font-size: 85%;
        line-height: 1.45;
        background-color: #f6f8fa;
        border-radius: 6px;
        margin-bottom: 16px;
    }

    .markdown-body >>> code {
        font-size: 85%;
        background-color: rgba(175, 184, 193, 0.2);
        border-radius: 3px;
        padding: 0.2em 0.4em;
    }

    .markdown-body >>> pre code {
        background-color: transparent;
        padding: 0;
    }

    .markdown-body >>> a {
        color: #0366d6;
        text-decoration: none;
    }

    .markdown-body >>> a:hover {
        text-decoration: underline;
    }

    .markdown-body >>> strong {
        font-weight: 600;
    }

    .markdown-body >>> em {
        font-style: italic;
    }

    .markdown-body >>> img {
        max-width: 100%;
        box-sizing: content-box;
        background-color: #fff;
    }

    .markdown-body >>> table {
        border-spacing: 0;
        border-collapse: collapse;
        display: block;
        width: 100%;
        overflow: auto;
        margin-bottom: 16px;
    }

    .markdown-body >>> th {
        font-weight: 600;
        padding: 6px 13px;
        border: 1px solid #dfe2e5;
    }

    .markdown-body >>> td {
        padding: 6px 13px;
        border: 1px solid #dfe2e5;
    }

    .markdown-body >>> tr {
        background-color: #fff;
        border-top: 1px solid #c6cbd1;
    }

    .markdown-body >>> tr:nth-child(2n) {
        background-color: #f6f8fa;
    }
</style>
